<?php
JunctionFileCabinet::using("Junction_Core_Factory");
JunctionFileCabinet::using("Junction_Mapping_Core");
JunctionFileCabinet::using("Junction_Utils_Mapping");
JunctionFileCabinet::using("Junction_Utils_Reflection_Facade");

/**
 * Wrap around a client object in order to deal with dependent data.
 * 
 * <p>This class could also be used to track changes in the future.
 * 
 * TODO This class obviously does not obey any interface but fudges it
 * at run time using reflection.  This might be a problem and should
 * be discussed.  One obvious thing to do is restrict the use of this
 * class to only getters/setters defined in the mapping, but that would
 * mean that any other methods defined in the decorated class' interface
 * would be obscured.  Another option is to do a check:
 * 	1. Does the method exist as a getter and setter
 * 		if so then execute using mapping
 * 	2. Otherwise does the method exist on the decorated class
 * 		if so then execute it
 *  3. Throw an exception.
 */
class Junction_Core_Decorator {

	/**
	 * @var Object which is decorated, has getters and setters
	 */
	private $_decorated;
	
	/**
	 * @var Junction_Mapping_Core
	 */
	private $_mapping;

	public function __construct($classname, $object = null) {
		if (!isset($object)) {
			$object = Junction_Utils_Reflection_Facade::getInstance($classname, array());
		}
		$this->_decorated = $object;
		$this->_mapping = Junction_Core_Factory::construct($classname);
	}
	/**
	 * Catch a method invocation and route it appropriately.
	 * 
	 * Case 1. The method is a read (e.g. getPassword)
	 * 	# perform a read with the mapping core
	 *   # if the requested property is scalar then invoke 
	 * 	   the corresponding getter on _decorated
	 *   # if the requested property is complex then defer
	 *     responsibility to a dependent mapping 
	 * Case 2: The method is a write (e.g. setPassword)
	 * 	# perform a write with the mapping core
	 * 	 # if the requested property is scalar then invoke
	 * 	   the corresponding setter on _decorated
	 *   # if the requested property is complex then ...
	 *     we need to decide how to handle this case,
	 *     right now do nothing.
	 *
	 * @param String $method
	 * @param array $params
	 * @return unknown
	 */
	public function __call($method, array $params) {
		$property = $this->getPropertyNameFrom($method);
		if ($this->_mapping->contains($property)) {
			if ($this->isReadMethod($method)) {
				return $this->_mapping->read($property, $this->_decorated);
			}
			return $this->_mapping->write($property, $params, $this->_decorated);
		}
		if (Junction_Utils_Reflection_Facade::methodExists($this->_decorated, $method)) {
			return Junction_Utils_Reflection_Facade::invokeArgs($this->_decorated, $method, $params);
		}
		throw new Exception("Invalid method called"); // TODO clean up this exception.
	}
	
	private function getPropertyNameFrom($method) {
		return Junction_Utils_Mapping::getPropertyNameFrom($method);
	}
	
	private function isReadMethod($method) {
		return Junction_Utils_Mapping::isGetMethod($method);
	}
}
?>